#   Trolls Outta Luckland v1.1.0
#   Copyright 2009-2010, Gummbum
#
#   This file is part of Trolls Outta Luckland.
#
#   Trolls Outta Luckland is free software: you can redistribute it
#   and/or modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation, either version 3 of
#   the License, or (at your option) any later version.
#
#   Trolls Outta Luckland is distributed in the hope that it will be
#   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
#   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with Trolls Outta Luckland.  If not, see
#   <http://www.gnu.org/licenses/>.

__version__ = '1.1.0'

## Python imports
import os
import sys
import time

try:
    ## Pygame imports
    import pygame
    ## Game library imports
    import spritesheet
    from pixelperfect import get_colorkey_hitmask
    from utils import import_module
except ImportError, e:
    print 'ImportError: %s' % e
    print '    in file "%s"' % os.path.abspath(sys.argv[0])

_IMAGE_CACHE = {}
_HITMASK_CACHE = {}

class SpriteStripAnim(object):
    """sprite strip animator
    
    This class provides an iterator (iter() and next() methods), and a
    __add__() method for joining strips which comes in handy when a
    strip wraps to the next row.
    """
    def __init__(self, filename, rect, count, colorkey=None, loop=False,
    frames=1, hitmasks=False, angles=None, resize=True, name=None):
        """construct a SpriteStripAnim
        
        filename, rect, count, and colorkey are the same arguments used
        by spritesheet.load_strip.
        
        loop is a boolean that, when True, causes the next() method to
        loop. If False, the terminal case raises StopIteration.
        
        frames is the number of ticks to return the same image before
        the iterator advances to the next image.
        
        If hitmasks is True the images' hitmasks will be generated and
        tuple(image,hitmask) will be returned by next(). Otherwise
        no hitmasks are generated and next() returns just an image.
        
        angles is an iterable specifying angles in degrees. If angles is
        not None rotated images and (if specified) hitmasks will be
        generated. In this context next() will return
        tuple(dict(images),dict(hitmasks)) instead of tuple(image,hitmask).
        The dict keys are the angle used in the rotation.
        
        resize specifies whether to crop the rotated image to the original
        image's rect. See pygame.transform.rotate form more info.
        """
        self.cache_key = name
        self.filename = filename
        self.colorkey = colorkey
        self.i = 0
        self.loop = loop
        self.frames = int(frames)
        self.f = int(frames)
        self.hitmasks = hitmasks
        self.angles = angles
        self.resize = resize
        if self.cache_key in _IMAGE_CACHE:
            self.images = _IMAGE_CACHE[self.cache_key]
        else:
            ss = spritesheet.spritesheet(filename)
            self.images = ss.load_strip(rect, count, colorkey)
            _IMAGE_CACHE[self.cache_key] = self.images
        if angles is not None:
            self.make_rotations()
        elif hitmasks:
            self.make_hitmasks()
    def has_hitmasks(self):
        return self.hitmasks is not False
    def has_angles(self):
        return self.angles is not None
    def length(self):
        if self.has_angles():
            if len(self.images) > 0:
                return len(self.images[0])
            else:
                return 0
        else:
            return len(self.images)
    def length_hitmasks(self):
        if self.has_hitmasks():
            return len(self.hitmasks)
        else:
            return 0
    def length_angles(self):
        if self.has_angles():
            return len(self.angles)
        else:
            return 0
    def iter(self):
        self.i = 0
        self.f = self.frames
        return self
    def next(self):
        if self.i >= self.length():
            if self.loop is False:
                raise StopIteration
            else:
                self.i = 0
        image = self.images[self.i]
        if self.hitmasks is not False:
            hitmask = self.hitmasks[self.i]
        self.f -= 1
        if self.f == 0:
            self.i += 1
            self.f = self.frames
        if self.hitmasks is not False:
            return image,hitmask
        else:
            return image
    def get(self, i):
        if self.has_hitmasks():
            return self.images[i],self.hitmasks[i]
        else:
            return self.images[i]
    def get_angle(self, i, angle):
        if self.has_hitmasks():
            images = self.images[i][angle]
            hitmasks = self.hitmasks[i][angle]
            return images,hitmasks
        else:
            return self.images[i][angle]
    def make_hitmasks(self):
        if self.cache_key in _HITMASK_CACHE:
            self.hitmasks = _HITMASK_CACHE[self.cache_key]
            return
        self.hitmasks = []
        for image in self.images:
## Thanks, Michael
##            rect = image.get_rect()
##            colorkey = image.get_colorkey()
##            self.hitmasks.append(
##                get_colorkey_hitmask(image, rect, colorkey))
            self.hitmasks.append(image)
        _HITMASK_CACHE[self.cache_key] = self.hitmasks
    def make_rotations(self):
        if self.cache_key in _IMAGE_CACHE and \
        self.cache_key in _HITMASK_CACHE:
            self.hitmasks = _HITMASK_CACHE[self.cache_key]
            return
        images = self.images
        self.images = []
        if self.has_hitmasks():
            self.hitmasks = []
        for i in range(len(images)):
            image = images[i]
            rot_images = {}
            if self.has_hitmasks():
                rot_hitmasks = {}
            for angle in self.angles:
                rot_image = pygame.transform.rotate(image, angle)
                rot_rect = rot_image.get_rect()
                rot_images[angle] = rot_image
                if self.has_hitmasks():
                    rot_hitmask = get_colorkey_hitmask(
                        rot_image, rot_rect, rot_image.get_colorkey())
                    rot_hitmasks[angle] = rot_hitmask
            self.images.append(rot_images)
            if self.has_hitmasks():
                self.hitmasks.append(rot_hitmasks)
        _IMAGE_CACHE[self.cache_key] = self.images
        if self.has_hitmasks():
            _HITMASK_CACHE[self.cache_key] = self.hitmasks
    def dump_hitmasks(self):
        for i in range(len(_HITMASK_CACHE[self.cache_key])):
            f = open(os.path.join('lib','%s%d.py'%(self.cache_key,i)), 'wb')
            f.write('DICT = {')
            walk_dict(f, _HITMASK_CACHE[self.cache_key][i])
            f.write('}')
            f.close()
    def load_hitmasks(self, name, count):
        """load hitmasks from a file created by dump_hitmasks"""
        if self.has_hitmasks():
            return
        for i in range(count):
            mod = import_module('%s%d'%(name,i))
            if name not in _HITMASK_CACHE:
                _HITMASK_CACHE[name] = []
            _HITMASK_CACHE[name].append(mod.DICT)
            del mod
        self.hitmasks =_HITMASK_CACHE[name]
    def clear_caches(self):
        del _IMAGE_CACHE[self.cache_key]
        del _HITMASK_CACHE[self.cache_key]
    def load_single(self, rect, colorkey=None):
        ## Future: untested
        ss = spritesheet.spritesheet(filename)
        image = ss.image_at(rect, colorkey)
        rect = image.get_rect()
        self.images.append(image)
        if has_hitmasks():
            self.hitmasks.append(get_colorkey_hitmask(image, rect, colorkey))
    def __add__(self, ss):
        self.images = self.images + ss.images
        if self.hitmasks is not False:
            self.hitmasks = self.hitmasks + ss.hitmasks
        return self

def walk_dict(f, d, level=1):
    w = f.write
    pad = ' '*level
    for key in d.keys():
        if type(d[key]) is dict:
            w('\n'+pad+repr(key)+' : {')
            walk_dict(f, d[key], level+1)
            w('},')
        elif type(d[key]) is list:
            w('\n'+pad+repr(key)+' : [')
            walk_list(f, d[key], level+1)
            w('],')
        else:
            print '\nERROR',type(HC[cache_key])
    
def walk_list(f, ls, level=1):
    w = f.write
    pad = ' '*level
    for el in ls:
        if type(el) is dict:
            w('\n'+pad+'{')
            walk_dict(f, el, level+1)
            w('},')
        elif type(el) is list:
            w('\n'+pad+'[')
            walk_list(f, el, level+1)
            w('],')
        else:
            w(repr(el)+',')
